﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Routing;
using System.Web.Mvc;

namespace Tencent.OA.App
{
    /// <summary>
    /// 定义一个用于解析控制器类型的接口。
    /// </summary>
    public interface IControllerTypeResolver
    {
        /// <summary>
        /// 根据指定的Area名称和控制器名称，获取对应的控制器类型。
        /// </summary>
        /// <param name="areaName">Area名称</param>
        /// <param name="controllerName">控制器名称</param>
        /// <returns>控制器类型。</returns>
        Type ResolveControllerType(string areaName, string controllerName);
    }

    /// <summary>
    /// 提供 IControllerTypeResolver 接口的默认实现。
    /// </summary>
    public class DefaultControllerTypeResolver : IControllerTypeResolver
    {
        private RouteCollection m_routeCollection;

        /// <summary>
        /// 初始化 <see cref="DefaultControllerTypeResolver"/> 类的新实例。
        /// </summary>
        public DefaultControllerTypeResolver()
        {
            Cache = new Dictionary<string, Type>();
        }

        /// <summary>
        /// 用于存储缓存对象的字典集合。
        /// </summary>
        /// <value>字典集合。</value>
        protected IDictionary<string, Type> Cache
        {
            get;
            private set;
        }

        /// <summary>
        /// 获取或设置为 ASP.NET 路由操作提供路由的集合。
        /// </summary>
        public RouteCollection RouteCollection
        {
            get
            {
                if (this.m_routeCollection == null)
                {
                    this.m_routeCollection = RouteTable.Routes;
                }
                return this.m_routeCollection;
            }
            set
            {
                this.m_routeCollection = value;
            }
        }

        /// <summary>
        /// 根据指定的Area名称和控制器名称，获取对应的控制器类型。
        /// </summary>
        /// <param name="areaName">Area名称</param>
        /// <param name="controllerName">控制器名称</param>
        /// <returns>获取到的控制器类型。</returns>
        public Type ResolveControllerType(string areaName, string controllerName)
        {
            // 如果缓存中存在该Type对象则直接返回
            //
            string cacheKey = areaName + "_" + controllerName;
            bool hasCachedControllerType = Cache.ContainsKey(cacheKey);
            if (hasCachedControllerType)
            {
                return Cache[cacheKey];
            }

            // 首先从RouteCollection中查找指定的namespace，如果没有查找到则使用默认的namespace
            IEnumerable<string> areaNamespaces = FindNamespacesForArea(areaName, this.RouteCollection);
            Type controllerType = null;
            HashSet<string> namespaces = null;
            if (areaNamespaces != null)
            {
                namespaces = new HashSet<string>(areaNamespaces, StringComparer.OrdinalIgnoreCase);
            }
            else if (ControllerBuilder.Current.DefaultNamespaces.Count > 0)
            {
                namespaces = ControllerBuilder.Current.DefaultNamespaces;
            }
            controllerType = GetControllerTypeWithinNamespaces(areaName, controllerName, namespaces);

            if (!hasCachedControllerType)
            {
                Cache[cacheKey] = controllerType;
            }

            return controllerType;
        }

        /// <summary>
        /// 从路由集合中查找有关Area的命名空间信息。
        /// </summary>
        /// <param name="area">用于查找的Area名称。</param>
        /// <param name="routes">路由集合。</param>
        /// <returns>
        /// 一个 <see cref="string"/> 类型的Area命名空间集合。
        /// 如果查找到任何与 <param name="areaName" /> 匹配的命名空间，
        /// 则返回包含该命名空间的集合对象，否则返回 null 。
        /// </returns>
        protected IEnumerable<string> FindNamespacesForArea(string areaName, RouteCollection routes)
        {
            List<string> namespaces = new List<string>();

            // RouteCollection为Route的集合，Route包含一系列路由相关的信息，
            // 需要从Route对象中查找DataTokens包含的area、namespaces的名称，
            // 如果area名称与参数的名称匹配，则该namespaces为需要查找的值。
            foreach (var route in routes.OfType<Route>().Where(r => r.DataTokens != null))
            {
                if ((route.DataTokens["area"] != null
                     && route.DataTokens["area"].ToString() == areaName
                     && route.DataTokens["Namespaces"] != null)
                    || (route.DataTokens["area"] == null && route.DataTokens["Namespaces"] != null))
                {
                    namespaces.AddRange((IEnumerable<string>)route.DataTokens["Namespaces"]);
                }
            }
            if (namespaces.Count > 0)
                return namespaces;
            return null;
        }

        /// <summary>
        /// 在指定的 <paramref name="namespaces" /> 范围内查找控制器的类型对象。
        /// </summary>
        /// <param name="areaName">Area 名称。</param>
        /// <param name="controllerName">控制器名称。</param>
        /// <param name="namespaces">指定的命名空间集合。</param>
        /// <returns>
        /// 一个指定命名空间下的 <see cref="Type"/> 实例。
        /// </returns>
        protected virtual Type GetControllerTypeWithinNamespaces(string areaName, string controllerName, HashSet<string> namespaces)
        {
            ICollection<Type> matchingTypes = GetMatchingTypes(controllerName, namespaces);
            switch (matchingTypes.Count)
            {
                case 0:
                    // 没有查找到匹配的控制器类型返回null。
                    return null;
                case 1:
                    // 查找到唯一符合的控制器类型
                    return matchingTypes.First();

                default:
                    // 查找到多个时抛出异常
                    throw CreateAmbiguousControllerException(controllerName, matchingTypes);
            }
        }

        // 使用指定的控制器名称和命名空间集合从当前应用程序域中查找控制器Type对象
        //
        private ICollection<Type> GetMatchingTypes(string controllerName, HashSet<string> namespaces)
        {
            HashSet<Type> matchingTypes = new HashSet<Type>();
            IEnumerable<Type> possibleTypes = null;
            if (namespaces != null)
            {
                foreach (string ns in namespaces)
                {
                    string controllerNamespace = ns.Replace("*", "");

                    // 从当请应用程序域执行上下文的程序集中查找匹配命名空间的Assembly
                    // 
                    var possibleAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a =>
                        {
                            try
                            {
                                return a.GetTypes().Where(t => t.Namespace != null && t.Namespace.StartsWith(controllerNamespace)).Count() > 0;
                            }
                            catch
                            {
                                return false;
                            }
                        });

                    possibleTypes = from a in possibleAssemblies
                                    let types = a.GetTypes().Where(t => t.Namespace != null && t.Namespace.StartsWith(controllerNamespace))
                                    //let types = from ta in a.GetTypes() where (ta.Namespace != null && ta.Namespace.StartsWith(controllerNamespace)) select ta
                                    from t in types
                                    select t;
                }
            }
            else
            {
                var possibleAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a =>
                       {
                           try
                           {
                               return a.GetTypes().Where(t => t.Name != null && t.Name == controllerName + "Controller").Count() > 0;
                           }
                           catch
                           {
                               return false;
                           }
                       });

                possibleTypes = from a in possibleAssemblies
                                let types = a.GetTypes().Where(t => t.Name != null && t.Name == controllerName + "Controller")
                                from t in types
                                select t;

            }
            if (possibleTypes != null)
            {
                foreach (var possibleType in possibleTypes)
                {
                    if (possibleType.Name == controllerName + "Controller")
                    {
                        matchingTypes.Add(possibleType);
                    }
                }
            }
            return matchingTypes;
        }

        // 查找到多个Controller，抛出该异常，同时需显示所有的控制器名称。
        // 参考 mvc源代码：DefaultControllerFactory.CreateAmbiguousControllerException
        //
        private InvalidOperationException CreateAmbiguousControllerException(string controllerName, ICollection<Type> matchingTypes)
        {
            StringBuilder typeList = new StringBuilder();
            foreach (Type matchedType in matchingTypes)
            {
                typeList.AppendLine();
                typeList.Append(matchedType.FullName);
            }

            string errorText = String.Format(@"查找到多个与指定名称 (&apos;{0}&apos;) 相匹配的控制器类型。是否当前请求的路径信息不能通过指定命名空间在路由表中进行查找，如果是这样请在通过 &apos;MapRoute&apos; 方法注册路由信息时指定&apos;namespaces&apos; 参数。
    查找到与指定名称 &apos;{0}&apos; 相匹配的控制器类型有：{1}",
                controllerName, typeList);

            return new InvalidOperationException(errorText);
        }

        #region IControllerTypeResolver 成员

        Type IControllerTypeResolver.ResolveControllerType(string areaName, string controllerName)
        {
            return this.ResolveControllerType(areaName, controllerName);
        }

        #endregion

    }

}
